package com.yanghui.dtracing.core.opentracing;

import cn.hutool.core.util.StrUtil;
import com.yanghui.dtracing.core.opentracing.log.LogData;
import io.opentracing.Span;
import io.opentracing.tag.Tag;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Getter
public class DTracerSpan implements Span {

    private final DTracerSpanContext spanContext;

    private final DTracer tracer;

    private final String serviceName;

    @Setter
    private String className;

    private String operationName;

    private final long startTime;

    private long endTime;

    private long costTime;

    private Map<String,String> strTags = new HashMap<>();

    private Map<String,Boolean> bolTags = new HashMap<>();

    private Map<String,Number> numTags = new HashMap<>();

    private List<LogData> logDataList = new ArrayList<>();

    protected DTracerSpan(DTracer tracer,DTracerSpanContext spanContext,  String serviceName,
                       String className, String operationName, long startTime,
                       Map<String, Object> tags) {
        this.tracer = tracer;
        this.spanContext = spanContext;
        this.serviceName = serviceName;
        this.className = className;
        this.operationName = operationName;
        this.startTime = startTime;
        parseTags(tags);
    }

    public boolean isRootSpan(){
        return DTracerSpanContext.ROOT_SPAN_ID.equals(this.context().toSpanId());
    }

    private void parseTags(Map<String, Object> tags) {
        if(null == tags){
            return;
        }
        for(Map.Entry<String,Object> entry : tags.entrySet()){
            if(entry.getValue() instanceof String){
                strTags.put(entry.getKey(),entry.getValue().toString());
            }
            if(entry.getValue() instanceof Boolean){
                bolTags.put(entry.getKey(),(Boolean) entry.getValue());
            }
            if (entry.getValue() instanceof Number){
                numTags.put(entry.getKey(),(Number) entry.getValue());
            }
        }
    }

    @Override
    public DTracerSpanContext context() {
        return this.spanContext;
    }

    @Override
    public Span setTag(String key, String value) {
        strTags.put(key,value);
        return this;
    }

    @Override
    public Span setTag(String key, boolean value) {
        bolTags.put(key,value);
        return this;
    }

    @Override
    public Span setTag(String key, Number value) {
        numTags.put(key,value);
        return this;
    }

    @Override
    public <T> Span setTag(Tag<T> tag, T value) {
        if(null == value){
            return this;
        }
        if(value instanceof Boolean){
            this.setTag(tag.getKey(),(Boolean) value);
        }else if(value instanceof String){
            this.setTag(tag.getKey(),(String) value);
        }else if(value instanceof Number){
            this.setTag(tag.getKey(),(Number) value);
        }
        return this;
    }

    @Override
    public Span log(Map<String, ?> fields) {
        this.log(System.currentTimeMillis(),fields);
        return this;
    }

    @Override
    public Span log(long timestampMicroseconds, Map<String, ?> fields) {
        this.getLogDataList().add(new LogData(timestampMicroseconds,fields));
        return this;
    }

    @Override
    public Span log(String event) {
        this.log(System.currentTimeMillis(),event);
        return this;
    }

    @Override
    public Span log(long timestampMicroseconds, String event) {
        Map<String, Object> fields = new HashMap<>();
        fields.put(LogData.EVENT_TYPE_KEY,event);
        this.log(timestampMicroseconds,fields);
        return this;
    }

    @Override
    public Span setBaggageItem(String key, String value) {
        this.spanContext.addBaggageItem(key,value);
        return this;
    }

    @Override
    public String getBaggageItem(String key) {
        return this.spanContext.getBaggageItem(key);
    }

    @Override
    public Span setOperationName(String operationName) {
        if(StrUtil.isNotEmpty(operationName)){
            return this;
        }
        this.operationName = operationName;
        return this;
    }

    @Override
    public void finish() {
        this.finish(System.currentTimeMillis());
    }

    @Override
    public void finish(long finishMicros) {
        this.endTime = finishMicros;
        this.costTime = this.endTime - this.startTime;
        ScopeContext.close(this.spanContext.getSpanId());
        this.tracer.report(this);
    }
}
